%%%-----------------------------------------------------------------------------
%%% @doc
%%% apn_server core.
%%%
%%% @end
%%%-----------------------------------------------------------------------------
-module (apn_server_core).

-include("apns.hrl").

-define(SERVER, ?MODULE).
-define(EPOCH, 62167219200).
-define(MAX_PAYLOAD, 2048).

-define(CLOSE, 0).
-define(OPEN,  1).


% 0 正常
% 1 生成证书失败
% 2 appkey的推送已经存在，不能重复上传
% 3 开启失败，appkey已经开启，不能重复开启
% 4 关闭推送失败，没有已开启的推送进程
% 5 开启或者关闭失败，没有相关证书
% 6 程序异常
% 7 删除失败，开启状态不能删除证书

-type status() :: no_errors
                | processing_error
                | missing_token
                | missing_topic
                | missing_payload
                | missing_token_size
                | missing_topic_size
                | missing_payload_size
                | invalid_token
                | unknown.
-export_type([status/0]).

-type connection() :: #apns_connection{}.
-export_type([connection/0]).

-type conn_id() :: atom() | pid().
-export_type([conn_id/0]).

-type apns_str() :: binary() | string().
-type alert() :: apns_str() | #loc_alert{}.
-export_type([alert/0]).

-type msg() :: #apns_msg{}.
-export_type([msg/0]).

-export ([create_connect/5, call_apn_server/4, action_connect/3, delete_pem/2]).

-export([send_message/2]).
-export([message_id/0, expiry/1, timestamp/1]).
-export([dev_default_connection/0, pro_default_connection/0]).


%% web网站上传证书后，调用该接口，开启apns推送功能
create_connect(Appkey, CerPath, P12Path, Type, Action) ->
    {ok, DevCertFileOpts} = application:get_env(apn_server, dev_cert_file),
    {ok, ProCertFileOpts} = application:get_env(apn_server, pro_cert_file),
    {ok, DevCertFileList} = file:list_dir(DevCertFileOpts),
    {ok, ProCertFileList} = file:list_dir(ProCertFileOpts),
    case lists:member(Appkey++ ".pem", DevCertFileList ++ ProCertFileList) of
        true ->
            lager:error("Appkey:~p.pem file duplicate", [Appkey]),
            2;
        false ->
            Result = os:cmd(lists:concat(["/opt/etcloud/apn_server/bin/gen_make_pem.sh ", CerPath, " ", P12Path, " ", Appkey, " ", Type])),
            lager:debug("Make pem result:~p", [Result]),
            ResultList = string:tokens(Result,"\r\n"),
            case lists:member("MAC verified OK", ResultList) of
                true ->
                    case Action of
                        ?OPEN  ->
                            case apn_server_connect_manager:create_connect(Appkey, Type) of
                                {ok, _} -> 
                                    0;
                                Error -> 
                                    lager:error("create_connect fail, error~p", [Error]),
                                    6
                            end;
                        ?CLOSE ->
                            0
                    end;
                false ->
                    lager:error("createconnect fail, error~p", [Result]),
                    1
            end
    end.

action_connect(Appkey, Type, Action) ->
    {ok, DevCertFileOpts} = application:get_env(apn_server, dev_cert_file),
    {ok, ProCertFileOpts} = application:get_env(apn_server, pro_cert_file),
    {ok, DevCertFileList} = file:list_dir(DevCertFileOpts),
    {ok, ProCertFileList} = file:list_dir(ProCertFileOpts),
    case lists:member(Appkey ++ ".pem", DevCertFileList ++ ProCertFileList) of
        true ->
            case Action of
                ?OPEN ->
                    case apn_server_connect_pool:lookup(list_to_atom(Appkey)) of
                        undefined ->
                            case apn_server_connect_manager:create_connect(Appkey, Type) of
                                {ok, _} -> 
                                    0;
                                Error -> 
                                    lager:error("create_connect fail, error~p", [Error]),
                                    6
                            end;
                        _ ->
                            lager:error("Appkey:~p.pem pid duplicate connected", [Appkey]),
                            3
                    end;
                ?CLOSE ->
                    case apn_server_connect_pool:lookup(list_to_atom(Appkey)) of
                        undefined ->
                            lager:error("Appkey:~p.pem pid not connect", [Appkey]),
                            4;
                        Pid ->
                            apn_server_connection:stop(Pid),
                            0
                    end
            end;
        false ->
            lager:error("Appkey:~p.pem file not find", [Appkey]),
            5
    end.

delete_pem(Appkey, Type) ->
    case apn_server_connect_pool:lookup(list_to_atom(Appkey)) of
        undefined ->
            {ok, DevCertFileOpts} = case Type of
                "dev" ->
                    application:get_env(apn_server, dev_cert_file);
                "pro" ->
                    application:get_env(apn_server, pro_cert_file)
            end,
            Result = os:cmd(lists:concat(["rm -rf  ", DevCertFileOpts, Appkey,".pem"])),
            lager:debug("Make pem result:~p", [Result]),
            0;
        _ ->
            lager:error("Appkey:~p.pem pid connected, not delete pem", [Appkey]),
            7
    end.   


%% IM服务器通过调用该接口去推送apns消息
call_apn_server(Appkey, Token, Msg, Badge) when is_binary(Appkey) -> 
    call_apn_server(binary_to_list(Appkey), Token, Msg, Badge);    

call_apn_server(Appkey, Token, Msg, Badge) ->
    ApnsMag = #apns_msg{alert = Msg, badge = Badge, sound = "sound", device_token = Token},
    case apn_server_connect_pool:lookup(list_to_atom(Appkey)) of
        undefined ->
            lager:error("Appkey:~p.pem pid not connect", [Appkey]);
        Pid ->
            apn_server_connection:send_message(Pid, ApnsMag)
    end.

%% @doc Sends a message to Apple
-spec send_message(conn_id(), msg()) -> ok.
send_message(ConnId, Msg) ->
    apn_server_connection:send_message(ConnId, Msg).


%% @doc  Generates an "unique" and valid message Id
-spec message_id() -> binary().
message_id() ->
    {_, _, MicroSecs} = os:timestamp(),
    Secs = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
    First = Secs rem 65536,
    Last = MicroSecs rem 65536,
    <<First:2/unsigned-integer-unit:8, Last:2/unsigned-integer-unit:8>>.


-spec expiry(none | {{1970..9999, 1..12, 1..31}, {0..24, 0..60, 0..60}} | pos_integer()) -> non_neg_integer().
expiry(none) -> 0;
expiry(Secs) when is_integer(Secs) ->
    calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - ?EPOCH + Secs;
expiry(Date) ->
    calendar:datetime_to_gregorian_seconds(Date) - ?EPOCH.

-spec timestamp(pos_integer()) -> calendar:datetime().
timestamp(Secs) ->
    calendar:gregorian_seconds_to_datetime(Secs + ?EPOCH).


get_env(K, Def) ->
    case application:get_env(apn_server, K) of
        {ok, V} -> V;
        _ -> Def
    end.

dev_default_connection() ->
    DefaultConn = #apns_connection{},
    ErrorFun = case get_env(error_fun, DefaultConn#apns_connection.error_fun) of
        {M, F} -> 
            fun(I, S) -> M:F(I, S) end;
        Other -> 
            Other
    end, 
    FeedbackFun = case get_env(feedback_fun, DefaultConn#apns_connection.feedback_fun) of
        {M2, F2} -> 
            fun(T) -> M2:F2(T) end;
        Other2 -> 
            Other2
    end,
    DefaultConn#apns_connection{
        apple_host       = get_env(dev_apple_host, DefaultConn#apns_connection.apple_host), 
        apple_port       = get_env(apple_port, DefaultConn#apns_connection.apple_port), 
        key_file         = get_env(key_file, DefaultConn#apns_connection.key_file), 
        cert_file        = get_env(dev_cert_file, DefaultConn#apns_connection.cert_file), 
        cert_password    = get_env(cert_password, DefaultConn#apns_connection.cert_password), 
        timeout          = get_env(timeout, DefaultConn#apns_connection.timeout),
        feedback_host    = get_env(feedback_host, DefaultConn#apns_connection.feedback_host), 
        feedback_port    = get_env(feedback_port, DefaultConn#apns_connection.feedback_port), 
        expires_conn     = get_env(expires_conn,  DefaultConn#apns_connection.expires_conn), 
        feedback_timeout = get_env(feedback_timeout, DefaultConn#apns_connection.feedback_timeout), 
        error_fun        = ErrorFun,
        feedback_fun     = FeedbackFun,
        versions         = [  ]}.

pro_default_connection() ->
    DefaultConn = #apns_connection{},
    ErrorFun = case get_env(error_fun, DefaultConn#apns_connection.error_fun) of
        {M, F} -> 
            fun(I, S) -> M:F(I, S) end;
        Other -> 
            Other
    end, 
    FeedbackFun = case get_env(feedback_fun, DefaultConn#apns_connection.feedback_fun) of
        {M2, F2} -> 
            fun(T) -> M2:F2(T) end;
        Other2 -> 
            Other2
    end,
    DefaultConn#apns_connection{
        apple_host       = get_env(pro_apple_host, DefaultConn#apns_connection.apple_host), 
        apple_port       = get_env(apple_port, DefaultConn#apns_connection.apple_port), 
        key_file         = get_env(key_file, DefaultConn#apns_connection.key_file), 
        cert_file        = get_env(pro_cert_file, DefaultConn#apns_connection.cert_file), 
        cert_password    = get_env(cert_password, DefaultConn#apns_connection.cert_password), 
        timeout          = get_env(timeout, DefaultConn#apns_connection.timeout),
        feedback_host    = get_env(feedback_host, DefaultConn#apns_connection.feedback_host), 
        feedback_port    = get_env(feedback_port, DefaultConn#apns_connection.feedback_port), 
        expires_conn     = get_env(expires_conn,  DefaultConn#apns_connection.expires_conn), 
        feedback_timeout = get_env(feedback_timeout, DefaultConn#apns_connection.feedback_timeout), 
        error_fun        = ErrorFun,
        feedback_fun     = FeedbackFun,
        versions         = ['tlsv1']}.
